/*!
 * jQuery Raty - A Star Rating Plugin
 * ------------------------------------------------------------------
 *
 * jQuery Raty is a plugin that generates a customizable star rating.
 *
 * Licensed under The MIT License
 *
 * @version        2.5.2
 * @since          2010.06.11
 * @author         Washington Botelho
 * @documentation  wbotelhos.com/raty
 *
 * ------------------------------------------------------------------
 *
 *  <div id="star"></div>
 *
 *  $('#star').raty();
 *
 */

(function ($) {
	var methods = {
		init: function (settings) {
			return this.each(function () {
				methods.destroy.call(this);

				this.opt = $.extend(true, {}, $.fn.raty.defaults, settings);

				var that = $(this),
					inits = ["number", "readOnly", "score", "scoreName"];

				methods._callback.call(this, inits);

				if (this.opt.precision) {
					methods._adjustPrecision.call(this);
				}

				this.opt.number = methods._between(
					this.opt.number,
					0,
					this.opt.numberMax
				);

				this.opt.path = this.opt.path || "";

				if (
					this.opt.path &&
					this.opt.path.charAt(this.opt.path.length - 1) !== "/"
				) {
					this.opt.path += "/";
				}

				this.stars = methods._createStars.call(this);
				this.score = methods._createScore.call(this);

				methods._apply.call(this, this.opt.score);

				var space = this.opt.space ? 4 : 0,
					width =
						this.opt.width ||
						this.opt.number * this.opt.size + this.opt.number * space;

				if (this.opt.cancel) {
					this.cancel = methods._createCancel.call(this);

					width += this.opt.size + space;
				}

				if (this.opt.readOnly) {
					methods._lock.call(this);
				} else {
					that.css("cursor", "pointer");
					methods._binds.call(this);
				}

				if (this.opt.width !== false) {
					that.css("width", width);
				}

				methods._target.call(this, this.opt.score);

				that.data({ settings: this.opt, raty: true });
			});
		},
		_adjustPrecision: function () {
			this.opt.targetType = "score";
			this.opt.half = true;
		},
		_apply: function (score) {
			if (score && score > 0) {
				score = methods._between(score, 0, this.opt.number);
				this.score.val(score);
			}

			methods._fill.call(this, score);

			if (score) {
				methods._roundStars.call(this, score);
			}
		},
		_between: function (value, min, max) {
			return Math.min(Math.max(parseFloat(value), min), max);
		},
		_binds: function () {
			if (this.cancel) {
				methods._bindCancel.call(this);
			}

			methods._bindClick.call(this);
			methods._bindOut.call(this);
			methods._bindOver.call(this);
		},
		_bindCancel: function () {
			methods._bindClickCancel.call(this);
			methods._bindOutCancel.call(this);
			methods._bindOverCancel.call(this);
		},
		_bindClick: function () {
			var self = this,
				that = $(self);

			self.stars.on("click.raty", function (evt) {
				self.score.val(
					self.opt.half || self.opt.precision ? that.data("score") : this.alt
				);

				if (self.opt.click) {
					self.opt.click.call(self, parseFloat(self.score.val()), evt);
				}
			});
		},
		_bindClickCancel: function () {
			var self = this;

			self.cancel.on("click.raty", function (evt) {
				self.score.removeAttr("value");

				if (self.opt.click) {
					self.opt.click.call(self, null, evt);
				}
			});
		},
		_bindOut: function () {
			var self = this;

			$(this).on("mouseleave.raty", function (evt) {
				var score = parseFloat(self.score.val()) || undefined;

				methods._apply.call(self, score);
				methods._target.call(self, score, evt);

				if (self.opt.mouseout) {
					self.opt.mouseout.call(self, score, evt);
				}
			});
		},
		_bindOutCancel: function () {
			var self = this;

			self.cancel.on("mouseleave.raty", function (evt) {
				$(this).attr("src", self.opt.path + self.opt.cancelOff);

				if (self.opt.mouseout) {
					self.opt.mouseout.call(self, self.score.val() || null, evt);
				}
			});
		},
		_bindOverCancel: function () {
			var self = this;

			self.cancel.on("mouseover.raty", function (evt) {
				$(this).attr("src", self.opt.path + self.opt.cancelOn);

				self.stars.attr("src", self.opt.path + self.opt.starOff);

				methods._target.call(self, null, evt);

				if (self.opt.mouseover) {
					self.opt.mouseover.call(self, null);
				}
			});
		},
		_bindOver: function () {
			var self = this,
				that = $(self),
				action = self.opt.half ? "mousemove.raty" : "mouseover.raty";

			self.stars.on(action, function (evt) {
				var score = parseInt(this.alt, 10);

				if (self.opt.half) {
					var position = parseFloat(
							(evt.pageX - $(this).offset().left) / self.opt.size
						),
						plus = position > 0.5 ? 1 : 0.5;

					score = score - 1 + plus;

					methods._fill.call(self, score);

					if (self.opt.precision) {
						score = score - plus + position;
					}

					methods._roundStars.call(self, score);

					that.data("score", score);
				} else {
					methods._fill.call(self, score);
				}

				methods._target.call(self, score, evt);

				if (self.opt.mouseover) {
					self.opt.mouseover.call(self, score, evt);
				}
			});
		},
		_callback: function (options) {
			for (var i in options) {
				if (typeof this.opt[options[i]] === "function") {
					this.opt[options[i]] = this.opt[options[i]].call(this);
				}
			}
		},
		_createCancel: function () {
			var that = $(this),
				icon = this.opt.path + this.opt.cancelOff,
				cancel = $("<img />", {
					src: icon,
					alt: "x",
					title: this.opt.cancelHint,
					class: "raty-cancel",
				});

			if (this.opt.cancelPlace == "left") {
				that.prepend("&#160;").prepend(cancel);
			} else {
				that.append("&#160;").append(cancel);
			}

			return cancel;
		},
		_createScore: function () {
			return $("<input />", {
				type: "hidden",
				name: this.opt.scoreName,
			}).appendTo(this);
		},
		_createStars: function () {
			var that = $(this);

			for (var i = 1; i <= this.opt.number; i++) {
				var title = methods._getHint.call(this, i),
					icon = this.opt.score && this.opt.score >= i ? "starOn" : "starOff";

				icon = this.opt.path + this.opt[icon];

				$("<img />", { src: icon, alt: i, title: title }).appendTo(this);

				if (this.opt.space) {
					that.append(i < this.opt.number ? "&#160;" : "");
				}
			}

			return that.children("img");
		},
		_error: function (message) {
			$(this).html(message);

			$.error(message);
		},
		_fill: function (score) {
			var self = this,
				hash = 0;

			for (var i = 1; i <= self.stars.length; i++) {
				var star = self.stars.eq(i - 1),
					select = self.opt.single ? i == score : i <= score;

				if (self.opt.iconRange && self.opt.iconRange.length > hash) {
					var irange = self.opt.iconRange[hash],
						on = irange.on || self.opt.starOn,
						off = irange.off || self.opt.starOff,
						icon = select ? on : off;

					if (i <= irange.range) {
						star.attr("src", self.opt.path + icon);
					}

					if (i == irange.range) {
						hash++;
					}
				} else {
					var icon = select ? "starOn" : "starOff";

					star.attr("src", this.opt.path + this.opt[icon]);
				}
			}
		},
		_getHint: function (score) {
			var hint = this.opt.hints[score - 1];
			return hint === "" ? "" : hint || score;
		},
		_lock: function () {
			var score = parseInt(this.score.val(), 10), // TODO: 3.1 >> [['1'], ['2'], ['3', '.1', '.2']]
				hint = score ? methods._getHint.call(this, score) : this.opt.noRatedMsg;

			$(this).data("readonly", true).css("cursor", "").attr("title", hint);

			this.score.attr("readonly", "readonly");
			this.stars.attr("title", hint);

			if (this.cancel) {
				this.cancel.hide();
			}
		},
		_roundStars: function (score) {
			var rest = (score - Math.floor(score)).toFixed(2);

			if (rest > this.opt.round.down) {
				var icon = "starOn"; // Up:   [x.76 .. x.99]

				if (this.opt.halfShow && rest < this.opt.round.up) {
					// Half: [x.26 .. x.75]
					icon = "starHalf";
				} else if (rest < this.opt.round.full) {
					// Down: [x.00 .. x.5]
					icon = "starOff";
				}

				this.stars
					.eq(Math.ceil(score) - 1)
					.attr("src", this.opt.path + this.opt[icon]);
			} // Full down: [x.00 .. x.25]
		},
		_target: function (score, evt) {
			if (this.opt.target) {
				var target = $(this.opt.target);

				if (target.length === 0) {
					methods._error.call(this, "Target selector invalid or missing!");
				}

				if (this.opt.targetFormat.indexOf("{score}") < 0) {
					methods._error.call(this, 'Template "{score}" missing!');
				}

				var mouseover = evt && evt.type == "mouseover";

				if (score === undefined) {
					score = this.opt.targetText;
				} else if (score === null) {
					score = mouseover ? this.opt.cancelHint : this.opt.targetText;
				} else {
					if (this.opt.targetType == "hint") {
						score = methods._getHint.call(this, Math.ceil(score));
					} else if (this.opt.precision) {
						score = parseFloat(score).toFixed(1);
					}

					if (!mouseover && !this.opt.targetKeep) {
						score = this.opt.targetText;
					}
				}

				if (score) {
					score = this.opt.targetFormat.toString().replace("{score}", score);
				}

				if (target.is(":input")) {
					target.val(score);
				} else {
					target.html(score);
				}
			}
		},
		_unlock: function () {
			$(this)
				.data("readonly", false)
				.css("cursor", "pointer")
				.removeAttr("title");

			this.score.removeAttr("readonly", "readonly");

			for (var i = 0; i < this.opt.number; i++) {
				this.stars.eq(i).attr("title", methods._getHint.call(this, i + 1));
			}

			if (this.cancel) {
				this.cancel.css("display", "");
			}
		},
		cancel: function (click) {
			return this.each(function () {
				if ($(this).data("readonly") !== true) {
					methods[click ? "click" : "score"].call(this, null);
					this.score.removeAttr("value");
				}
			});
		},
		click: function (score) {
			return $(this).each(function () {
				if ($(this).data("readonly") !== true) {
					methods._apply.call(this, score);

					if (!this.opt.click) {
						methods._error.call(
							this,
							'You must add the "click: function(score, evt) { }" callback.'
						);
					}

					this.opt.click.call(this, score, $.Event("click"));

					methods._target.call(this, score);
				}
			});
		},
		destroy: function () {
			return $(this).each(function () {
				var that = $(this),
					raw = that.data("raw");

				if (raw) {
					that
						.off(".raty")
						.empty()
						.css({ cursor: raw.style.cursor, width: raw.style.width })
						.removeData("readonly");
				} else {
					that.data("raw", that.clone()[0]);
				}
			});
		},
		getScore: function () {
			var score = [],
				value;

			$(this).each(function () {
				value = this.score.val();

				score.push(value ? parseFloat(value) : undefined);
			});

			return score.length > 1 ? score : score[0];
		},
		readOnly: function (readonly) {
			return this.each(function () {
				var that = $(this);

				if (that.data("readonly") !== readonly) {
					if (readonly) {
						that.off(".raty").children("img").off(".raty");

						methods._lock.call(this);
					} else {
						methods._binds.call(this);
						methods._unlock.call(this);
					}

					that.data("readonly", readonly);
				}
			});
		},
		reload: function () {
			return methods.set.call(this, {});
		},
		score: function () {
			return arguments.length
				? methods.setScore.apply(this, arguments)
				: methods.getScore.call(this);
		},
		set: function (settings) {
			return this.each(function () {
				var that = $(this),
					actual = that.data("settings"),
					news = $.extend({}, actual, settings);

				that.raty(news);
			});
		},
		setScore: function (score) {
			return $(this).each(function () {
				if ($(this).data("readonly") !== true) {
					methods._apply.call(this, score);
					methods._target.call(this, score);
				}
			});
		},
	};

	$.fn.raty = function (method) {
		if (methods[method]) {
			return methods[method].apply(
				this,
				Array.prototype.slice.call(arguments, 1)
			);
		} else if (typeof method === "object" || !method) {
			return methods.init.apply(this, arguments);
		} else {
			$.error("Method " + method + " does not exist!");
		}
	};

	$.fn.raty.defaults = {
		cancel: false,
		cancelHint: "Cancel this rating!",
		cancelOff: "cancel-off.png",
		cancelOn: "cancel-on.png",
		cancelPlace: "left",
		click: undefined,
		half: false,
		halfShow: true,
		hints: ["bad", "poor", "regular", "good", "gorgeous"],
		iconRange: undefined,
		mouseout: undefined,
		mouseover: undefined,
		noRatedMsg: "Not rated yet!",
		number: 5,
		numberMax: 20,
		path: "",
		precision: false,
		readOnly: false,
		round: { down: 0.25, full: 0.6, up: 0.76 },
		score: undefined,
		scoreName: "score",
		single: false,
		size: 20,
		space: true,
		starHalf: "images/star-half.png",
		starOff: "images/star.svg",
		starOn: "images/star-selected.svg",
		target: undefined,
		targetFormat: "{score}",
		targetKeep: false,
		targetText: "",
		targetType: "hint",
		width: undefined,
	};
})(jQuery);
